import {
  accessify,
  beType,
  beField,
  beTraits,
} from './entry'

import {
  Class,
  Slots,
  Type,
} from '../ast/types'

import sign from '../ast/sign'

const
  L       = sign['<'],
  R       = sign['>'],
  COMMA   = sign[','],
  IGNORE  = sign['_'],

  OF      = sign.of,
  EXTENDS = sign.extends,
  TRAITS  = sign.traits

function soFields (s, node, sign) {

  s.lead()
  while (sign) {
    let
      field = beField(s, sign),
      name = field.id && field.id.normalize() || ''

    if (name && node.items.some(
      x => x.id && x.id.normalize() === name
    ))
      s.exists('the field', field.id)

    node.append(field)
    s.beZero(s.soMore())
    sign = s.isAlign() && s.right() || 0
  }

  return node
}

function beClass (s) {
  let
    node = new Class().start(s.start)

  while (true) {
    let
      x = beType(s, s.sps().beMore()),
      name = x.normalize(),
      sign = s.soMore()

    if (node.items.some(x => x.normalize() === name))
      s.exists('class of', x)

    node.append(x)
    if (!sign) return node.end(x.loc.end)
    if (sign !== COMMA) s.fail(sign)
  }
}

function beSlots (s) {
  // type generic<slot1, slot2>
  let
    node = new Slots().start(s.start),
    x = s.sps().beId()
  while (true) {
    let
      name = x.normalize(),
      sign = s.beRight()

    if (node.items.some(x => x.normalize() === name))
      s.exists('slots', x)

    node.append(x)
    if (sign === R) break
    if (sign !== COMMA) s.fail(sign)
    x = s.sps().beId()
  }
  return node.end(s.end)
}

export default function (s) {
  let
    start  = s.start,
    access = accessify(s.text()),
    id     = s.sps().beId(),
    sign   = s.soRight(),
    base   = null,
    traits = null

  if (sign === OF) {
    let x = beClass(s)
    return new Type(access, id, x, null)
      .start(start).end(x.loc.end)
  }

  if (sign === L)
    return soFields(
      s,
      new Type(access, id, beSlots(s), null).start(start),
      s.beNext()
    )

  if (sign === EXTENDS) {
    base = beType(s, s.beRight())
    sign = s.soRight()
  }

  if (sign === TRAITS) {
    traits = beTraits(s)
    sign = s.soRight()
  }

  s.beZero(sign)

  let node = new Type(access, id, base, traits)
  sign = s.soMore()
  return sign
    && soFields(s, node, sign)
    || node.start(start).end((traits || base || id).loc.end)
}
